package net.chromedpork.scraper.configuration;



import java.util.Map;
import java.util.HashMap;

import javax.xml.xpath.XPathConstants;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import net.chromedpork.hypertext.validation.Assertion;



/**
 *
 *  Scraper configuration parser.
 * 
 *  @author Inode <inode@chromedpork.net>
 * 
 */

public class ConfigurationParser extends AbstractConfigurationParser
{
    private static String[] arguments;


    /**
     *
     *  Constructs a configuration parser which will substitute
     *  parameters in an XML document with the passed arguments.
     *
     *  @param arguments    document-specific arguments
     *
     */

    ConfigurationParser(String... arguments)
    {
        this.arguments = arguments;
    }


    /**
     *
     *  Enumerate HTTP request configuration from an XML document
     *
     *  @param config       XML document containing scraper configuration
     *
     *  @return Map containing method and URL keys and values
     *
     *  @throws ConfigurationException
     * 
     */

    protected static Map<String, String> parseRequest(Document config)
              throws ConfigurationException
    {
        return (parseAttributeMap(config, "/scraper/request"));
    }


    /**
     *
     *  Enumerate HTTP request header configuration from an XML document
     *
     *  @param config       XML document containing scraper configuration
     *
     *  @return Map containing header keys and values
     *
     *  @throws ConfigurationException
     * 
     */

    protected static Map<String, String> parseHeaders(Document config)
              throws ConfigurationException
    {
        return (parseMapTag(config, "/scraper/request/header"));
    }


    /**
     *
     *  Enumerate HTTP request payload configuration from an XML document
     *
     *  @param config       XML document containing scraper configuration
     *
     *  @return Map containing payload data keys and values
     *
     *  @throws ConfigurationException
     *
     */
    
    protected static Map<String, String> parseData(Document config)
              throws ConfigurationException
    {
        return (parseMapTag(config, "/scraper/request/data"));
    }


    /**
     *
     *  Enumerate HTTP response payload filter configuration from
     *  an XML document
     *
     *  @param config       XML document containing scraper configuration
     *
     *  @return Map containing filter type key and value
     *
     *  @throws ConfigurationException
     * 
     */

    protected static Map<String, String> parseFilter(Document config)
              throws ConfigurationException
    {
        return (parseAttributeMap(config, "/scraper/filter"));
    }


    /**
     *
     *  Enumerate HTTP response payload filter expression configuration
     *  from an XML document
     *
     *  @param config       XML document containing scraper configuration
     *
     *  @return Map containing payload expression keys, values, and an
     *          optional name of an attribute to return the value of
     *
     *  @throws ConfigurationException
     * 
     */

    protected static Map<String, String> parseExpression(Document config)
              throws ConfigurationException
    {
        return (parseMapTag(config, "/scraper/filter/expression", "return"));
    }


    /**
     *
     *  Enumerate XML tag attribute keys and values.
     *
     *  @param config       XML document containing scraper configuration
     *  @param expression   XPath expression
     *
     *  @return Map containing attribute names and values
     *
     *  @throws ConfigurationException
     *
     */
    
    private static Map<String, String> parseAttributeMap(Document config,
                                                         String expression)
            throws ConfigurationException
    {
        Assertion.notNull(config, "configuration document is null");
        Assertion.notNull(expression, "XPath expression is null");


        Map<String, String> map = new HashMap<String, String>();


        try
        {
            NodeList nodes = (NodeList)evaluateXPath(config,
                                                     expression,
                                                     XPathConstants.NODESET);
            
            for (int i = 0; i < nodes.getLength(); i++)
            {
                NamedNodeMap attributes = nodes.item(i).getAttributes();

                for (int j = 0; j < attributes.getLength(); j++)
                {
                    String key   = attributes.item(j).getNodeName();
                    String value = attributes.item(j).getNodeValue();

                    if (arguments != null)
                    {
                        for (int k = 0; k < arguments.length; k++)
                        {
                            key   = key.replaceAll("\\[" + k + "\\]", arguments[k]);
                            value = value.replaceAll("\\[" + k + "\\]", arguments[k]);
                        }
                    }

                    map.put(key, value);
                }
            }
        }

        catch (ConfigurationException exception)
        {
            throw new ConfigurationException("Attribute map parsing failed");
        }


        return (map);
    }


    /**
     *
     *  Enumerate XML tag attribute values, where attributes are "key" and
     *  "value" respectively
     *
     *  @param config       XML document containing scraper configuration
     *  @param expression   XPath expression
     *
     *  @return Map containing values for "key" and "value" attributes
     *
     *  @throws ConfigurationException
     *
     */
    
    private static Map<String, String> parseMapTag(Document config,
                                                   String expression)
            throws ConfigurationException
    {
        return (parseMapTag(config, expression, null));
    }


    /**
     *
     *  Enumerate XML tag attribute values, where attributes are "key" and
     *  "value" respectively, as well as an optional extra attribute.
     *
     *  @param config           XML document containing scraper configuration
     *  @param expression       XPath expression
     *  @param extraAttribute   extra attribute to gather value of
     *
     *  @return Map containing values for "key" and "value" attributes,
     *          as well as the value of "extra" if specified.
     *
     *  @throws ConfigurationException
     *
     */
    
    private static Map<String, String> parseMapTag(Document config,
                                                   String expression,
                                                   String extraAttribute)
            throws ConfigurationException
    {
        Assertion.notNull(config, "configuration document is null");
        Assertion.notNull(expression, "XPath expression is null");

        
        Map<String, String> map = new HashMap<String, String>();


        try
        {
            NodeList nodes = (NodeList)evaluateXPath(config,
                                                     expression,
                                                     XPathConstants.NODESET);
             
            for (int i = 0; i < nodes.getLength(); i++)
            {
                Node key;
                Node value;
                
                NamedNodeMap attributes = nodes.item(i).getAttributes();
                
                if (attributes.getLength() < 2)
                {
                    throw new ConfigurationException("Attribute count < 2");
                }

                if ((key = attributes.getNamedItem("key")) == null)
                {
                    throw new ConfigurationException("Missing key");
                }

                if ((value = attributes.getNamedItem("value")) == null)
                {
                    throw new ConfigurationException("Missing value");
                }

                String keyString   = key.getNodeValue();
                String valueString = value.getNodeValue();

                if (arguments != null)
                {
                    for (int j = 0; j < arguments.length; j++)
                    {
                        keyString   = keyString.replaceAll("\\[" + j + "\\]", arguments[j]);
                        valueString = valueString.replaceAll("\\[" + j + "\\]", arguments[j]);
                    }
                }

                map.put(keyString, valueString);

                if (extraAttribute != null)
                {
                    if ((value = attributes.getNamedItem(extraAttribute)) != null)
                    {
                        keyString   = value.getNodeName();
                        valueString = value.getNodeValue();

                        if (arguments != null)
                        {
                            for (int j = 0; j < arguments.length; j++)
                            {
                                keyString   = keyString.replaceAll("\\[" + j + "\\]", arguments[j]);
                                valueString = valueString.replaceAll("\\[" + j + "\\]", arguments[j]);
                            }
                        }

                        map.put(keyString, valueString);
                    }
                }
            }
        }

        catch (ConfigurationException exception)
        {
            throw new ConfigurationException("Map tag parsing failed");
        }

        return (map);
    }
}
